package org.haredot.utils;

import org.haredot.annotation.Column;
import org.haredot.callback.PreparedStatementBatchSetter;
import org.haredot.callback.PreparedStatementCallback;
import org.haredot.callback.PreparedStatementSetter;
import org.haredot.callback.ResultSetCallback;
import org.haredot.exception.DaoAccessException;

import java.lang.reflect.Field;
import java.sql.*;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class DBUtils {

    /**
     * 获取数据库的连接对象,
     *  每一个业务 提供一个连接， 一个线程 绑定一个连接
     *  业务完成后， 关闭连接
     *
     *  ThreadLocal (本地线程)
     *      ThreadLocal 可以绑定一个 临界资源、在每一个线程中，都会 提供一个 临界资源的副本 以供使用。
     *
     *  Connection 是 线程 不安全的，所在 connection 如果是 临界资源(共享资源)，那么就会产生并发访问的问题。
     *
     */
    private static ThreadLocal<Connection> threadLocal = new ThreadLocal<>() ;


    public static Connection getConnection() throws DaoAccessException {
        // 从 threadLocal 中 获取 中 获取 连接
        Connection conn = threadLocal.get();
        if (conn == null) {
            try {
                Class.forName(JdbcPropertiesUtils.getProperty("db.driver.name"));
                String url = JdbcPropertiesUtils.getProperty("db.url");
                String username = JdbcPropertiesUtils.getProperty("db.username");
                String password = JdbcPropertiesUtils.getProperty("db.password");

                conn = DriverManager.getConnection(url, username, password);
                threadLocal.set(conn);
            } catch (Exception e) {
                throw new DaoAccessException(e);
            }
        }
        return conn ;
    }

    /**
     * 支持 增、删、改 操作，返回影响的行数
     * @param sql : 要处理的 SQL 命令
     * @param params : SQL 中需要的参数
     * @return
     */
    public static int update(String sql, Object... params) throws DaoAccessException {
        Connection conn = null ;
        PreparedStatement ps = null ;
        try {
            conn = getConnection();
            ps = conn.prepareStatement(sql);
            // 绑定参数
            for(int i=0 ; i < params.length; i++) {
                ps.setObject(i+1, params[i]);
            }
            // 执行 SQL
            return ps.executeUpdate();
        }catch (Exception e){
            throw new DaoAccessException(e);
        } finally {
            close(null, ps, conn);
        }
    }

    /**
     *
     * @param entity
     * @param sql  占位符 由 ? 更改为 #{key},  key 是 entity 对象中的 属性名
     * @param <T>
     * @return
     * @throws DaoAccessException
     */
    public static <T> int update(T entity, String sql) throws DaoAccessException {
        // 解析 sql 中的 #{ key } , 获取 所有的 key, 并替换成 ?
        String regex = "#\\{\\s*(\\w+)\\s*\\}" ;

        Pattern pattern = Pattern.compile(regex);
        Matcher m = pattern.matcher(sql) ;

        StringBuilder sb = new StringBuilder();
        // keys中 存储的是 SQL 中 需要注入的值 的属性名
        List<String> keys = new ArrayList<>();
        while(m.find()) {
            // 获取正则表达式第一组匹配的内容，获取 占位符中的 key 属性名
            String key = m.group(1);
            keys.add(key);
            m.appendReplacement(sb, "?") ;
        }
        // 拼接 尾部内容
        m.appendTail(sb);

        // 获取 替换后的 SQL
        sql = sb.toString() ;

        Connection conn = null ;
        PreparedStatement ps = null ;

        try {
            conn = getConnection();
            ps = conn.prepareStatement(sql);

            // 绑定参数, key 代表 实体中的属性名
            Class<?> aClass = entity.getClass();
            int index = 0 ;
            for(String key : keys) {
                Field field = ClassUtils.getAllFields(aClass).stream().filter(f -> Objects.equals(f.getName(), key))
                        .findFirst().orElse(null) ;

                if (field == null) throw new Exception("key 对应的属性在" + aClass.getName() + "中没有找到");
                // 允许访问私有属性
                field.setAccessible(true);
                // 获取属性对应的值
                Object val = field.get(entity);
                // 绑定数据
                ps.setObject(++index , val);
            }
            // 执行 SQL
            return ps.executeUpdate() ;
        }catch (Exception e){
            throw new DaoAccessException(e);
        } finally {
            close(null, ps, conn);
        }
    }

    /**
     * 查询多条记录
     * @param sql
     * @param params
     * @param <T>
     * @return
     * @throws DaoAccessException
     */
    public static <T> List<T> queryForList(String sql, Class<T> clazz ,Object...params) throws DaoAccessException {
        Connection conn = null ;
        PreparedStatement ps = null ;
        ResultSet rs = null ;
        try {
            conn = getConnection() ;
            ps = conn.prepareStatement(sql);
            // 绑定参数
            for(int i=0 ; i < params.length; i++) {
                ps.setObject(i+1, params[i]);
            }
            // 执行SQL 并获取结果集
            rs = ps.executeQuery();
            // 判断 是否 查询到了数据
            List<T> ret = new ArrayList<>();
            while(rs.next()) {
                // 如果有值，创建一个对象
                T obj = clazz.getConstructor().newInstance();
                // 获取 查询的字段个数
                ResultSetMetaData metaData = rs.getMetaData();
                int columnCount = metaData.getColumnCount();

                for(int i = 1 ; i<= columnCount ; i++) {
                    // 获取查询的列名
                    String columnName = metaData.getColumnLabel(i) ;
                    // 获取 clazz 类对象 的所有属性
                    List<Field> allFields = ClassUtils.getAllFields(clazz);
                    // 根据 查询的列名，获取字段名
                    Field field = allFields.stream().filter(f -> {

                        // 获取 field 属性上的注解 @Column
                        if (f.isAnnotationPresent(Column.class) ) {
                            // 获取 注解
                            Column column = f.getAnnotation(Column.class);
                            // 获取 column 中的 value 成员
                            if (Objects.equals(column.value(), "")) {
                                if (Objects.equals(f.getName(), columnName)) return true ;
                            }else{
                                if (Objects.equals(column.value(), columnName)) return true ;
                            }
                        }else{
                            // 如果 field 的属性名 和 查询的列名相同，则代表找到
                            if (Objects.equals(f.getName(), columnName)) return true ;
                        }
                        return false ;
                    }).findFirst().orElse(null);
                    // 如果找到属性，则给 该属性设置 值
                    if (field !=null) {
                        Object object = rs.getObject(columnName, field.getType());
                        field.setAccessible(true);
                        field.set(obj, object);
                    }
                }
                ret.add(obj);
            }
            return ret ;
        }catch (Exception e){
            throw new DaoAccessException(e);
        }finally {
            close(null, ps, conn);
        }
    }

    /**
     * 支持多表查询，返回多条数据
     * @param sql
     * @param params
     * @return
     * @throws DaoAccessException
     */
    public static List<Map<String, Object>> queryForList(String sql, Object...params) throws DaoAccessException {
        Connection conn = null ;
        PreparedStatement ps =null ;
        ResultSet rs = null ;
        try {
            conn = getConnection() ;
            ps = conn.prepareStatement(sql) ;
            for(int i=0 ; i < params.length; i++) {
                ps.setObject(i+1, params[i]);
            }
            // 执行 SQL
            rs = ps.executeQuery();

            List<Map<String, Object>> list = new ArrayList<>();
            while (rs.next()) {
                Map<String, Object> map = new HashMap<>();
                // 将查询的所有的列 放到 Map 中
                ResultSetMetaData metaData = rs.getMetaData();
                int columnCount = metaData.getColumnCount();

                for(int i = 1; i <= columnCount; i++) {
                    // 获取 查询的 列名
                    String columnName = metaData.getColumnLabel(i) ;
                    map.put(columnName, rs.getObject(columnName)) ;
                }

                list.add(map);
            }
            return list ;
        }catch (Exception e){
            throw new DaoAccessException(e);
        }finally {
            close(null, ps, conn);
        }
    }

    public static Map<String, Object> queryForMap(String sql, Object...params) throws DaoAccessException {
        List<Map<String, Object>> maps = queryForList(sql, params);

        if (maps.size() > 1) {
            throw new DaoAccessException("queryForMap 期待返回 1条数据、实际返回" + maps.size() + "条记录");
        }
        return maps.isEmpty() ? null : maps.get(0);
    }

    /**
     * 返回单条记录
     * @param sql
     * @param clazz
     * @param params
     * @param <T>
     * @return
     * @throws DaoAccessException
     */
    public static <T> T query(String sql, Class<T> clazz ,Object...params) throws DaoAccessException{
        List<T> ts = queryForList(sql, clazz, params);
        if (ts.size() > 1) {
            throw new DaoAccessException("query方法期待返回1条数据、实际返回" + ts.size() + "条记录");
        }
        return ts.isEmpty() ? null : ts.get(0) ;
    }


    public static <T> int executeBatch(String sql, List<T> data, PreparedStatementBatchSetter<T> setter)throws DaoAccessException {
        return executeBatch(sql, data, 100, setter);
    }

    /**
     * 执行插入命令，并获取插入的主键
     * @param sql
     * @param setter
     * @param <T>
     * @return
     */
    public static <T> T insertAndGeneratorKeys(String sql, PreparedStatementSetter<T> setter) throws DaoAccessException{
        Connection conn = null ;
        PreparedStatement ps = null ;
        ResultSet rs = null ;
        try {
            conn = getConnection();
            ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS) ;
            // 绑定参数
            setter.bindParams(ps);
            // 执行命令
            ps.execute();

            // 获取 插入的主键
            rs  = ps.getGeneratedKeys();

            if (rs.next()) {
                // 如何获取主键由 调用者 决定
                return setter.getGeneratorKey(rs) ;
            }
            return null ;
        }catch (Exception e){
            throw new DaoAccessException(e);
        }finally {
            close(null, ps, conn);
        }
    }

    /**
     *
     * @param sql
     * @param resultSetCallback
     * @param params
     * @param <T>
     * @return
     * @throws DaoAccessException
     */
    public static <T> T executeQuery(String sql, ResultSetCallback<T> resultSetCallback, Object...params) throws DaoAccessException{
        Connection conn = null ;
        PreparedStatement ps = null ;
        ResultSet rs = null ;
        try {
            conn = getConnection();
            ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS) ;
            // 绑定参数
            for(int i=0 ; i < params.length; i++) {
                ps.setObject(i+1, params[i]);
            }
            // 执行命令
            rs = ps.executeQuery();

            // 交由用户处理结果集
            return resultSetCallback.apply(rs);
        }catch (Exception e){
            throw new DaoAccessException(e);
        }finally {
            close(null, ps, conn);
        }
    }

    /**
     * 批量操作
     * @param sql
     * @param data
     * @param batchSize
     * @param setter
     * @param <T>
     * @return
     * @throws DaoAccessException
     */
    public static <T> int executeBatch(String sql, List<T> data, final long batchSize , PreparedStatementBatchSetter<T> setter)throws DaoAccessException {
        Connection conn = null ;
        PreparedStatement ps = null ;
        try {
            conn = getConnection();
            ps = conn.prepareStatement(sql) ;

            int size = 0 ;
            // 遍历每一条数据
            for(int i= 0 ; i < data.size(); i++) {
                // 给每一条数据 进行绑定
                setter.bindParams(ps, data.get(i), i) ;
                // 添加到 批处理中
                ps.addBatch();
                // 如果 (i + 1) 是 batchSize 的整数倍， 说明要进行 批量插入
                if ((i + 1) % batchSize == 0 || (i + 1) == data.size()) {
                    int[] counts = ps.executeBatch() ; // [1, 2, 1]
                    size += Arrays.stream(counts).sum() ;
                }
            }
            return size ;
        }catch (Exception e){
            throw new DaoAccessException(e);
        }finally {
            close(null, ps, conn);
        }
    }

    /**
     * @param sql
     * @param <E>
     * @throws DaoAccessException
     */
    public static <E> E execute(String sql, PreparedStatementCallback<E> callback) throws DaoAccessException {
        Connection connection = null;
        PreparedStatement ps = null ;
        try {
            connection = getConnection() ;
            ps = connection.prepareStatement(sql) ;

            // 此时 将剩余的操作 交由调用者处理
            return callback.apply(ps);
        }catch (Exception e){
            throw new DaoAccessException(e);
        }finally {
            close(null, ps, connection);
        }
    }


    /**
     * 关闭资源
     * @param rs
     * @param st
     * @param conn
     */
    public static void close(ResultSet rs , Statement st, Connection conn) throws DaoAccessException {
        try {
            if (rs!=null) {
                rs.close();
                rs = null ;
            }

            if (st !=null) {
                st.close();
                st = null ;
            }

        } catch (SQLException e) {
            throw new DaoAccessException();
        }
    }

    public static void closeConnection() throws DaoAccessException{
        // 从 threadLocal获取连接
        try {
            Connection connection = threadLocal.get();
            // 如果 连接 connection 不为空 ，则关闭连接
            if (connection != null) {

                connection.close();
                connection = null ;
                // 将 connection 从 ThreadLocal 中移除
                threadLocal.remove();
            }
        } catch (SQLException e) {
            throw new DaoAccessException(e);
        }
    }
}
